#include "stdafx.h"
#include "DynamicBindingTestSuite.h"
#include "TestActiveObject.h"

namespace CO { namespace SpiFoundation { namespace RoomFW {

using namespace SpiControl::Protocol;

DynamicBindingTestSuite::DynamicBindingTestSuite(void)
{
	LOG_TEST_ADD(DynamicBindingTestSuite::TestBindingWrapper)
	LOG_TEST_ADD(DynamicBindingTestSuite::TestDynamicBindingManager)
	LOG_TEST_ADD(DynamicBindingTestSuite::TestAll)
}

void DynamicBindingTestSuite::setup()
{
	LoggedTestSuite::setup();
}

void DynamicBindingTestSuite::tear_down()
{
	LoggedTestSuite::tear_down();
}

void DynamicBindingTestSuite::TestBindingWrapper()
{
	TestActiveObject objA;
	TestActiveObject objB;
	BindingSP innerBinding(new ActiveObjectsBinding(
		ActiveObjectSP(&objA, NullDeleter<ActiveObject>()), TestActiveInterface::PORT_MY_PORT_1,
		ActiveObjectSP(&objB, NullDeleter<ActiveObject>()), TestActiveInterface::PORT_MY_PORT_3));
	BindingWrapperSP bindingWrapper(new BindingWrapper(innerBinding));
	TEST_ASSERT(innerBinding.use_count() == 2);
	bindingWrapper->unbind();
	TEST_ASSERT(innerBinding.use_count() == 1);
}

// Message goes from portal to objA_port1, from objA_port2 to objB_port3 and back.
//
//     |-----------       -----------
//     |    A    |	      |    b    |
// |p|-----------|1|     |1|        |
//     |          |	      |         |
//     |         |2|---  |2|        |
//     |          |	  |   |         |
//     |         |3|  ---|3|        |
//     |          |	      |         |
//     |         |4|     |4|        |
//     |          |	      |         |
//     |-----------	      -----------
//
class TestDynamicBindingManagerMessageHandler : public ActiveObject::MessageHandler
{
public:
	TestDynamicBindingManagerMessageHandler(ActiveObject* parent) :
	  MessageHandler(parent)
	  {
	  }
	  virtual bool handle(RoomMessageSP message)
	  {
		  bool retVal = false;
		  switch (message->portIndex().port())
		  {
		  case TestActiveInterface::PORT_MY_PORT_1:
			  switch (message->messageID())
			  {
			  case GenericTestProtocol::IN_VOID_MESSAGE1:
				  // Recieved message on port 1, send message on port 2
				  this->sendMessage(TestActiveInterface::PORT_MY_PORT_2, GenericTestProtocol::OUT_VOID_MESSAGE1);
				  retVal = true;
				  break;
			  }
			  break;
		  case TestActiveInterface::PORT_MY_PORT_2:
			  switch (message->messageID())
			  {
			  case GenericTestProtocol::IN_VOID_MESSAGE1:
				  // Recieved message on port 2, send message on port 1
				  this->sendMessage(TestActiveInterface::PORT_MY_PORT_1, GenericTestProtocol::OUT_VOID_MESSAGE1);
				  retVal = true;
				  break;
			  }
			  break;
		  case TestActiveInterface::PORT_MY_PORT_3:
			  switch (message->messageID())
			  {
			  case GenericTestProtocol::OUT_VOID_MESSAGE1:
				  // Recieved message on port 3, reply.
				  message->reply(GenericTestProtocol::IN_VOID_MESSAGE1);
				  retVal = true;
				  break;
			  }
			  break;
		  case TestActiveInterface::PORT_MY_PORT_4: // Port 4 acts as control port.
			  switch (message->messageID())
			  {
			  case GenericTestProtocol::OUT_INT_MESSAGE1:
				  {
					  // Register port specified in message.
					  IntPayloadSP payload = ::boost::dynamic_pointer_cast<IntPayload, IPayload>(message->releasePayload());
					  ASSERT(0 != payload);
					  bool ret = this->registerDynamicPort(payload->value());
					  message->reply(GenericTestProtocol::IN_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(ret))));
					  retVal = true;
				  }
				  break;
			  case GenericTestProtocol::OUT_DYNAMIC_BIND:
				  {
					  // Dynamic bind to port specified in message.
					  DynamicBindPayloadSP payload = ::boost::dynamic_pointer_cast<DynamicBindPayload, IPayload>(message->releasePayload());
					  ASSERT(0 != payload);
					  DynamicBindingSP binding = this->dynamicBind(payload->first(), payload->second().c_str());
					  if (0 != binding)
					  {
						  bindings_.push_back(binding);
					  }
					  message->reply(GenericTestProtocol::IN_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(0 != binding))));
					  retVal = true;
				  }
				  break;
			  }
		  }
		  return retVal;
	  }
private:
	BindingList bindings_;
};
typedef ::boost::shared_ptr<TestDynamicBindingManagerMessageHandler> TestDynamicBindingManagerMessageHandlerSP;

void DynamicBindingTestSuite::TestDynamicBindingManager()
{
	//
	// Test registration.
	// Test binding objects.
	// Test binding portals.
	// Test unregister objects.
	// Test unregister portals.
	//

	// Get a ref once for convenience.
	DynamicBindingManager& mgr = DynamicBindingManager::Instance();

	// Set up object A with a message handler.
	TestActiveObject objA;
	TestDynamicBindingManagerMessageHandler mhA(&objA);
	objA.mapMessageHandler(PortIndex::INVALID, ActiveObject::MessageHandler::Ptr(&mhA, NullDeleter<TestDynamicBindingManagerMessageHandler>()));
	TEST_ASSERT(objA.start(DWait));
	
	// Set up object B with a message handler.
	TestActiveObject objB;
	TestDynamicBindingManagerMessageHandler mhB(&objB);
	objB.mapMessageHandler(PortIndex::INVALID, ActiveObject::MessageHandler::Ptr(&mhB, NullDeleter<TestDynamicBindingManagerMessageHandler>()));
	TEST_ASSERT(objB.start(DWait));

	// Register object A's first port.
	std::stringstream portNameA1;
	portNameA1 << objA.getObjectName() << '_' << objA.getPortName(TestActiveInterface::PORT_MY_PORT_1);
	mgr.registerDynamicPort(&objA, TestActiveInterface::PORT_MY_PORT_1, portNameA1.str().c_str());
	
	// Register object B's third port.
	std::stringstream portNameB;
	portNameB << objB.getObjectName() << '_' << objB.getPortName(TestActiveInterface::PORT_MY_PORT_3);
	mgr.registerDynamicPort(&objB, TestActiveInterface::PORT_MY_PORT_3, portNameB.str().c_str());

	// Use a binding list here so we treat the dynamic bindings like any others.
	BindingList bindings;

	// Create an event portal and Dynamic Bind to A1.
	EventPortal portal("TestPortal");
	TEST_ASSERT(portal.initialize());

	bindings.push_back(mgr.dynamicBind(&portal, portNameA1.str().c_str(), CTID_NULL, true));
	TEST_ASSERT(0 != bindings.back())

	// Dynamic Bind A2 to B3 (as though A were doing it)
	bindings.push_back(mgr.dynamicBind(&objA, TestActiveInterface::PORT_MY_PORT_2, portNameB.str().c_str()));
	TEST_ASSERT(0 != bindings.back())

	// Send message and verify response.
	try
	{
		portal.sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
	}
	catch (SpiFoundation::RoomFW::SendMessageError& e)
	{
		TEST_ASSERT_MSG(false, e.what());
	}
	TEST_ASSERT(portal.wait(DWait));
	RoomMessageSP reply = portal.getMessage();
	TEST_ASSERT(0 != reply);
	TEST_ASSERT(reply->messageID() == GenericTestProtocol::OUT_VOID_MESSAGE1);

	// Clean-up.
	TEST_ASSERT(objA.stop(DWait));
	TEST_ASSERT(objB.stop(DWait));
	mgr.unregister(&portal);
	mgr.unregister(&objA);
	mgr.unregister(&objB);
	TEST_ASSERT(!DynamicBindingManager::Instance().hasBindings());
	TEST_ASSERT(!DynamicBindingManager::Instance().hasRegistrations());
}

void DynamicBindingTestSuite::TestAll()
{
	//
	// Test registration.
	// Test binding objects.
	// Test binding portals.
	// Test unregister objects.
	// Test unregister portals.
	//

	// Set up object A with a message handler.
	TestActiveObjectSP objA(new TestActiveObject("objA"));
	TestDynamicBindingManagerMessageHandlerSP mhA(new TestDynamicBindingManagerMessageHandler(objA.get()));
	objA->mapMessageHandler(PortIndex::INVALID, mhA);
	TEST_ASSERT(objA->start(DWait));
	
	// Set up object B with a message handler.
	TestActiveObjectSP objB(new TestActiveObject("objB"));
	TestDynamicBindingManagerMessageHandlerSP mhB(new TestDynamicBindingManagerMessageHandler(objB.get()));
	objB->mapMessageHandler(PortIndex::INVALID, mhB);
	TEST_ASSERT(objB->start(DWait));

	// Use a binding list and treat the dynamic bindings like any others.
	BindingList bindings;

	// Use a normal binding to port 4 to tell objects to register their dynamic ports.
	EventPortalSP portalA4(new EventPortal("portalA4"));
	TEST_ASSERT(portalA4->initialize());
	bindings.push_back(BindingSP(new PortalBinding(portalA4, objA, TestActiveInterface::PORT_MY_PORT_4)));
	portalA4->sendMessage(GenericTestProtocol::OUT_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(TestActiveObject::PORT_MY_PORT_1))));
	TEST_ASSERT(portalA4->wait(DWait));
	RoomMessageSP replyMessage = portalA4->getMessage();
	TEST_ASSERT(0 != replyMessage);
	IntPayloadSP replyIntPayload = ::boost::static_pointer_cast<IntPayload, IPayload>(replyMessage->releasePayload());
	TEST_ASSERT(0 != replyIntPayload);
	TEST_ASSERT(replyIntPayload->value());

	// Use a normal binding to port 4 to tell objects to register their dynamic ports.
	EventPortalSP portalB4(new EventPortal("portalB4"));
	TEST_ASSERT(portalB4->initialize());
	bindings.push_back(BindingSP(new PortalBinding(portalB4, objB, TestActiveInterface::PORT_MY_PORT_4)));
	portalB4->sendMessage(GenericTestProtocol::OUT_INT_MESSAGE1, IPayload::ReleaseOwnership(IntPayloadSP(new IntPayload(TestActiveObject::PORT_MY_PORT_3))));
	TEST_ASSERT(portalB4->wait(DWait));
	replyMessage = portalB4->getMessage();
	TEST_ASSERT(0 != replyMessage);
	replyIntPayload = ::boost::static_pointer_cast<IntPayload, IPayload>(replyMessage->releasePayload());
	TEST_ASSERT(0 != replyIntPayload);
	TEST_ASSERT(replyIntPayload->value());

	// Create an event portal.
	EventPortalSP portal(new EventPortal("TestPortal"));
	TEST_ASSERT(portal->initialize());

	// Dynamic Bind portal to A1
	bindings.push_back(portal->dynamicBind(objA->defaultDynamicPortName(TestActiveInterface::PORT_MY_PORT_1).c_str(), CTID_NULL, true));
	TEST_ASSERT(0 != bindings.back())

	// Tell objA to Dynamic Bind A2 to B3.
	portalA4->sendMessage(GenericTestProtocol::OUT_DYNAMIC_BIND, IPayload::ReleaseOwnership(DynamicBindPayloadSP(
		new DynamicBindPayload(TestActiveObject::PORT_MY_PORT_2, objB->defaultDynamicPortName(TestActiveInterface::PORT_MY_PORT_3).c_str()))));
	TEST_ASSERT(portalA4->wait(DWait));
	replyMessage = portalA4->getMessage();
	TEST_ASSERT(0 != replyMessage);
	replyIntPayload = ::boost::static_pointer_cast<IntPayload, IPayload>(replyMessage->releasePayload());
	TEST_ASSERT(0 != replyIntPayload);
	TEST_ASSERT(replyIntPayload->value());

	// Send message and verify response.
	try
	{
		portal->sendMessage(GenericTestProtocol::IN_VOID_MESSAGE1);
	}
	catch (SpiFoundation::RoomFW::SendMessageError& e)
	{
		TEST_ASSERT_MSG(false, e.what());
	}
	TEST_ASSERT(portal->wait(DWait));
	RoomMessageSP reply = portal->getMessage();
	TEST_ASSERT(0 != reply);
	TEST_ASSERT(reply->messageID() == GenericTestProtocol::OUT_VOID_MESSAGE1);

	// Clean-up.
	TEST_ASSERT(objA->stop(DWait));
	TEST_ASSERT(objB->stop(DWait));
	bindings.clear();
	objA->unMapMessageHandler(PortIndex::INVALID, mhA);
	objB->unMapMessageHandler(PortIndex::INVALID, mhB);
	mhA.reset();
	mhB.reset();
	TEST_ASSERT(!DynamicBindingManager::Instance().hasBindings());
	objA.reset();
	objB.reset();
	portal.reset();
	TEST_ASSERT(!DynamicBindingManager::Instance().hasRegistrations());
}

}}}
